package org.c41.media;

import java.awt.Color;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;

import org.c41.system.C41System;
import org.c41.system.ImpossibleException;
import org.c41.system.Native;
import org.c41.util.ArrayBuilder;
import org.c41.util.Check;

@Native
public class MPlayer {

	public static final int STOPPED = 1;
	public static final int PAUSED = 2;
	public static final int PLAYING = 3;

	private static MPlayer one;

	public static MPlayer getInstance(){
		if(one == null){
			synchronized (MPlayer.class) {
				if(one == null){
					one = new MPlayer();
				}
			}
		}
		return one;
	}

	private int state;

	private Process mplayer;
	private BufferedReader err;
	private BufferedReader in;
	private PrintStream out;

	private int length;
	private int current_off;
	private int volume;
	private boolean mute;

	private Thread security;

	private MPlayer(){
		this.state=STOPPED;
	}

	public synchronized void play(Paramaters param) throws IOException{
		if(this.state!=STOPPED){
			m_call_illegal_state();
		}
		this.mplayer = C41System.exec(param.getParamaters());
		this.err = new BufferedReader(new InputStreamReader(mplayer.getErrorStream()));
		this.in = new BufferedReader(new InputStreamReader(mplayer.getInputStream()));
		this.out = new PrintStream(mplayer.getOutputStream());
		m_create_threads();
		this.state = PLAYING;
		this.volume=100;
	}

	private void m_create_threads() {
		Thread err_thread = new Thread(new Runnable() {

			@Override
			public void run() {
				m_run_err();
			}
		});
		Thread info_thread = new Thread(new Runnable(){

			@Override
			public void run() {
				m_run_info();
			}
		});
		Thread monitor_thread = new Thread(new Runnable() {

			@Override
			public void run() {
				m_run_monitor();
			}
		});
		err_thread.start();
		info_thread.start();
		monitor_thread.start();
		this.security = new Thread(new Runnable() {

			@Override
			public void run() {
				if(mplayer!=null){
					mplayer.destroy();
				}
			}
		});
		Runtime.getRuntime().addShutdownHook(security);
	}

	private void m_run_monitor(){
		try {
			mplayer.waitFor();
			m_call_finish();
		} catch (InterruptedException e) {
		}
	}

	private void m_call_finish() {
		this.state=STOPPED;
		Runtime.getRuntime().removeShutdownHook(security);
		mplayer=null;
		err=null;
		in=null;
		out=null;
		length=0;
		current_off=0;
		volume=0;
		mute=false;
		security=null;
	}

	private void m_run_info(){
		try {
			String text;
			while((text=in.readLine())!=null){
				m_set_info(text);
			}
		} catch (IOException e) {
		}
	}

	private void m_set_info(String text) {
		if(text.startsWith("ID_LENGTH")){
			String stub = text.substring(10, text.indexOf("."));
			this.length = Integer.valueOf(stub);
		}
		else if(text.startsWith("A:")){
			String stub = text.substring(2,text.indexOf(".")).trim();
			this.current_off=Integer.valueOf(stub);
		}
	}

	private void m_run_err(){
		try {
			while((err.readLine())!=null){
			}
		} catch (IOException e) {
		}
	}

	public synchronized void stop(){
		if(this.state!=PLAYING&&this.state!=PAUSED){
			m_call_illegal_state();
		}
		out.print("quit\n");
		out.flush();
	}

	public synchronized void pause(){
		if(this.state!=PLAYING){
			m_call_illegal_state();
		}
		this.state=PAUSED;
		out.print("pause\n");
		out.flush();
	}

	public synchronized void resume(){
		if(this.state!=PAUSED){
			m_call_illegal_state();
		}
		this.state=PLAYING;
		out.print("pause\n");
		out.flush();
	}

	public int getState(){
		return this.state;
	}

	public synchronized void setVolume(int volume){
		Check.between(volume, 0, 100);
		if(this.state==STOPPED){
			m_call_illegal_state();
		}
		out.print("volume "+volume+" 1\n");
		out.flush();
		this.volume=volume;
	}

	public synchronized void addVolume(int volume){
		if(this.state==STOPPED){
			m_call_illegal_state();
		}
		setVolume(this.volume+volume);
	}

	public synchronized void setMute(boolean b){
		if(this.state==STOPPED){
			m_call_illegal_state();
		}
		out.print("mute "+(b?"1":"0")+"\n");
		out.flush();
		this.mute=b;
	}

	public boolean isMute(){
		return this.mute;
	}

	public int getVolume(){
		return this.volume;
	}

	public synchronized void seek(int value, int type){
		if(this.state==STOPPED){
			m_call_illegal_state();
		}
		out.print("seek "+value+" "+type+"\n");
		out.flush();
	}

	public int getOffset(){
		return this.current_off;
	}

	public int getLength(){
		return this.length;
	}

	public static class Paramaters{

		String mplayer_path = "mplayer.exe";
		Color colorkey = new Color(3,3,3);
		String vo = "directx";
		long wid = -1L;
		int osd = 0;
		File file;

		public void setExecute(String path){
			this.mplayer_path=path;
		}

		public void setVO(String vo){
			this.vo=vo;
		}

		public void setWid(long hwnd){
			this.wid=hwnd;
		}

		public void setOSDLevel(int value){
			Check.between(value, 0, 3);
			this.osd=value;
		}

		public void setColorkey(Color color){
			this.colorkey=color;
		}

		public void setSource(File file){
			this.file=file;
		}

		public String[] getParamaters(){
			ArrayBuilder<String> result = ArrayBuilder.newInstance(String.class);
			result.append(mplayer_path);
			result.append("-vo").append(vo);
			result.append("-slave");
			result.append("-identify");
			if(wid!=-1L){
				result.append("-wid").append(String.valueOf(wid));
			}
			if(colorkey!=null){
				result.append("-colorkey").append(
						String.format("0x%02x%02x%02x", 
								colorkey.getRed(),
								colorkey.getGreen(),
								colorkey.getBlue())
						);
			}
			result.append("-osdlevel").append(String.valueOf(osd));
			result.append(file.getAbsolutePath());
			return result.toArray();
		}

	}

	private void m_call_illegal_state(){
		switch(this.state){
		case PLAYING: throw new IllegalStateException("PLAYING");
		case PAUSED: throw new IllegalStateException("PAUSED");
		case STOPPED: throw new IllegalStateException("STOPPED");
		default:throw new ImpossibleException();
		}
	}

}
